package com.mingqijia.gassafety.shared.utils;

import java.util.*;

/**
 * @author zhangfei
 * @version 1.0.0
 * @ClassName GraphUtil.java

 * @createTime 2021年04月16日 17:50:00
 */
public class GraphUtil {

    /**
     *
     */
    private static final double PRECISION = 2e-10;

    /**
     * 判断点是否在多边形内(基本思路是用交点法)
     *
     * @param point          点对象
     * @param boundaryPoints 边界点
     * @return boolean
     */
    public static boolean isPointInPolygon(final AmapPoint point, final AmapPoint[] boundaryPoints) {
        // 防止第一个点与最后一个点相同
        AmapPoint[] newBoundaryPoints = boundaryPoints;
        if (newBoundaryPoints != null && newBoundaryPoints.length > 0
                && newBoundaryPoints[newBoundaryPoints.length - 1].equals(newBoundaryPoints[0])) {
            newBoundaryPoints = Arrays.copyOf(newBoundaryPoints, newBoundaryPoints.length - 1);
        }
        int pointCount = newBoundaryPoints.length;

        // 首先判断点是否在多边形的外包矩形内，如果在，则进一步判断，否则返回false
        if (!isPointInRectangle(point, newBoundaryPoints)) {
            return false;
        }

        // 如果点与多边形的其中一个顶点重合，那么直接返回true
        for (int i = 0; i < pointCount; i++) {
            if (point.equals(newBoundaryPoints[i])) {
                return true;
            }
        }

        // 基本思想是利用X轴射线法，计算射线与多边形各边的交点， 如果是偶数，
        // 则点在多边形外，否则在多边形内。 还会考虑一些特殊情况， 如点在多边形顶点上 点在多边形边上等特殊情况。
        // X轴射线与多边形的交点数
        int intersectPointCount = 0;
        // X轴射线与多边形的交点权值
        // float intersectPointWeights = 0;
        // 浮点类型计算时候与0比较时候的容差
        double precision = PRECISION;
        // 边P1P2的两个端点
        AmapPoint point1 = newBoundaryPoints[0];
        AmapPoint point2 = null;
        AmapPoint amapPoint = null;
        // 循环判断所有的边
        for (int i = 1; i <= pointCount; i++) {
            point2 = newBoundaryPoints[i % pointCount];
            amapPoint = point1;

            // 如果点的y坐标在边P1P2的y坐标开区间范围之外，那么不相交。 latitude 纬度 纵坐标 longitude 经度 横坐标
            if (point.getLatitude() < Math.min(point1.getLatitude(), point2.getLatitude())
                    || point.getLatitude() > Math.max(point1.getLatitude(), point2.getLatitude())) {
                amapPoint = point1;
                point1 = point2;
                continue;
            }

            // 此处判断射线与边相交
            // 如果点的y坐标在边P1P2的y坐标开区间内
            if (point.getLatitude() > Math.min(point1.getLatitude(), point2.getLatitude())
                    && point.getLatitude() < Math.max(point1.getLatitude(), point2.getLatitude())) {
                // 若边P1P2是垂直的
                // longitude: 经度--纵坐标
                if (point1.getLongitude() == point2.getLongitude()) {
                    // 若点在垂直的边P1P2上，则点在多边形内
                    if (point.getLongitude() == point1.getLongitude()) {
                        return true;
                    } else if (point.getLongitude() < point1.getLongitude()) {
                        // 若点在垂直的边P1P2左边，则点与该边必然有交点
                        ++intersectPointCount;
                    }
                } else {
                    // 若边P1P2是斜线
                    if (point.getLongitude() <= Math.min(point1.getLongitude(), point2.getLongitude())) {
                        // 点point的x坐标在点P1和P2的左侧
                        ++intersectPointCount;
                    } else if (point.getLongitude() > Math.min(point1.getLongitude(), point2.getLongitude())
                            && point.getLongitude() < Math.max(point1.getLongitude(), point2.getLongitude())) {
                        // 点point的x坐标在点P1和P2的x坐标中间
                        double slopeDiff = 0.0d;
                        if (point1.getLatitude() > point2.getLatitude()) {
                            slopeDiff = AmapPoint.sub(
                                    AmapPoint.div(AmapPoint.sub(point.getLatitude(), point2.getLatitude()),
                                            AmapPoint.sub(point.getLongitude(), point2.getLongitude())),
                                    AmapPoint.div(AmapPoint.sub(point1.getLatitude(), point2.getLatitude()),
                                            AmapPoint.sub(point1.getLongitude(), point2.getLongitude())));
                        } else {
                            slopeDiff = AmapPoint.sub(
                                    AmapPoint.div(AmapPoint.sub(point.getLatitude(), point1.getLatitude()),
                                            AmapPoint.sub(point.getLongitude(), point1.getLongitude())),
                                    AmapPoint.div(AmapPoint.sub(point2.getLatitude(), point1.getLatitude()),
                                            AmapPoint.sub(point2.getLongitude(), point1.getLongitude())));
                        }
                        if (slopeDiff > 0) {
                            if (slopeDiff < precision) { // 由于double精度在计算时会有损失，
                                // 故匹配一定的容差。经试验，坐标经度可以达到0.0001
                                // 点在斜线P1P2上
                                return true;
                            } else {
                                // 点与斜线P1P2有交点
                                intersectPointCount++;
                            }
                        }
                    }
                }
            } else {
                // 边P1P2水平
                // latitude: 纵坐标
                if (point1.getLatitude() == point2.getLatitude()) {
                    if (point.getLongitude() <= Math.max(point1.getLongitude(), point2.getLongitude())
                            && point.getLongitude() >= Math.min(point1.getLongitude(), point2.getLongitude())) {
                        // 若点在水平的边P1P2上，则点在多边形内
                        return true;
                    }
                }
                // 判断点通过多边形顶点

                if (((point.getLatitude() == point1.getLatitude() && point.getLongitude() < point1.getLongitude()))
                        || (point.getLatitude() == point2.getLatitude() && point.getLongitude() < point2.getLongitude())) {
                    // 判断上一条边的另外一个端点与当前边的另外一个端点是否分布在水平射线的两侧.若是交点数+1
                    if (AmapPoint.mul(AmapPoint.sub(amapPoint.getLatitude(), point.getLatitude()),
                            AmapPoint.sub(point2.getLatitude(), point.getLatitude())) < 0) {
                        intersectPointCount++;
                    }
                    // if (point2.getLatitude() < point1.getLatitude()) {
                    // intersectPointWeights += -0.5;
                    // } else if (point2.getLatitude() > point1.getLatitude()) {
                    // intersectPointWeights += 0.5;
                    // }
                }
            }
            amapPoint = point1;
            point1 = point2;
        }
        // 偶数在多边形外
        // 奇数在多边形内
        return intersectPointCount % 2 != 0;
    }

    /**
     * 判断点是否在矩形内在矩形边界上，也算在矩形内(根据这些点，构造一个外包矩形)
     *
     * @param point          点对象
     * @param boundaryPoints 矩形边界点
     * @return boolean
     */
    public static boolean isPointInRectangle(final AmapPoint point, final AmapPoint[] boundaryPoints) {
        AmapPoint southWestPoint = getSouthWestPoint(boundaryPoints); // 西南角点
        AmapPoint northEastPoint = getNorthEastPoint(boundaryPoints); // 东北角点
        return (point.getLongitude() >= southWestPoint.getLongitude()
                && point.getLongitude() <= northEastPoint.getLongitude()
                && point.getLatitude() >= southWestPoint.getLatitude()
                && point.getLatitude() <= northEastPoint.getLatitude());

    }

    /**
     * 根据这组坐标，画一个矩形，然后得到这个矩形西南角的顶点坐标
     *
     * @param vertexs
     * @return AmapPoint
     */
    private static AmapPoint getSouthWestPoint(final AmapPoint[] vertexs) {
        double minLng = vertexs[0].getLongitude();
        double minLat = vertexs[0].getLatitude();
        for (AmapPoint amapPoint : vertexs) {
            double lng = amapPoint.getLongitude();
            double lat = amapPoint.getLatitude();
            if (lng < minLng) {
                minLng = lng;
            }
            if (lat < minLat) {
                minLat = lat;
            }
        }
        return new AmapPoint(minLng, minLat);
    }

    /**
     * 根据这组坐标，画一个矩形，然后得到这个矩形东北角的顶点坐标
     *
     * @param vertexs
     * @return AmapPoint
     */
    private static AmapPoint getNorthEastPoint(final AmapPoint[] vertexs) {
        double maxLng = 0.0d;
        double maxLat = 0.0d;
        for (AmapPoint amapPoint : vertexs) {
            double lng = amapPoint.getLongitude();
            double lat = amapPoint.getLatitude();
            if (lng > maxLng) {
                maxLng = lng;
            }
            if (lat > maxLat) {
                maxLat = lat;
            }
        }
        return new AmapPoint(maxLng, maxLat);
    }

    public boolean locationMatchGrid(List<String> gridList, Double lon,Double lat){
        for (String grid : gridList) {
            AmapPoint amapPoint = new AmapPoint();
            amapPoint.setLongitude(lon);
            amapPoint.setLatitude(lat);

            String[] electricFences = grid.split("#");
            AmapPoint[] pointArray;
            for (String electricFence : electricFences) {
                String[] locationList = electricFence.split(";");
                pointArray = new AmapPoint[locationList.length];
                for (int i = 0; i < locationList.length; i++) {
                    String[] coordinates = locationList[i].split(",");
                    AmapPoint point = new AmapPoint();
                    point.setLongitude(Double.parseDouble(coordinates[0]));
                    point.setLatitude(Double.parseDouble(coordinates[1]));
                    pointArray[i] = point;
                }
                if (pointArray.length > 1 && GraphUtil.isPointInPolygon(amapPoint,pointArray)) {
                    return true;
                }
            }
        }
        return false;
    }

    public Map<String,Double> getMaxAndMin(List<String> gridList){
        Map<String,Double> map = new HashMap();
        List<Double> lons = new ArrayList<>();
        List<Double> lats = new ArrayList<>();
        for (String grid : gridList) {
            String[] electricFences = grid.split("#");
            for (String electricFence : electricFences) {
                String[] locationList = electricFence.split(";");
                for (int i = 0; i < locationList.length; i++) {
                    String[] coordinates = locationList[i].split(",");
                    lons.add(Double.parseDouble(coordinates[0]));
                    lats.add(Double.parseDouble(coordinates[1]));
                }
            }
        }
        Double lonMax = Collections.max(lons);
        Double lonMin = Collections.min(lons);

        Double latMax = Collections.max(lats);
        Double latMin = Collections.min(lats);
        map.put("lonMax",lonMax);
        map.put("lonMin",lonMin);
        map.put("latMax",latMax);
        map.put("latMin",latMin);
        return map;
    }
}
